// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#ifndef ZIRCON_KERNEL_LIB_FBL_INCLUDE_FBL_NAME_H_
#define ZIRCON_KERNEL_LIB_FBL_INCLUDE_FBL_NAME_H_

#include <lib/zircon-internal/thread_annotations.h>
#include <string.h>
#include <zircon/compiler.h>
#include <zircon/types.h>

#include <fbl/algorithm.h>
#include <kernel/auto_lock.h>
#include <kernel/spinlock.h>

namespace fbl {

// A class for managing names of kernel objects. Since we don't want
// unbounded lengths, the constructor and setter perform
// truncation. Names include the trailing NUL as part of their
// Size-sized buffer.
template <size_t Size>
class Name {
 public:
  // Need room for at least one character and a NUL to be useful.
  static_assert(Size >= 1u, "Names must have size > 1");

  // Create an empty (i.e., "" with exactly 1 byte: a nul) Name.
  Name() {}

  // Create a name from the given data. This will be guaranteed to
  // be nul terminated, so the given data may be truncated.
  Name(const char* name, size_t len) { set(name, len); }

  ~Name() = default;

  // Copy the Name's data out. The written data is guaranteed to be
  // nul terminated, except when out_len is 0, in which case no data
  // is written.
  void get(size_t out_len, char* out_name) const __NONNULL((3)) {
    memset(out_name, 0, out_len);
    if (out_len > 0u) {
      AutoSpinLock lock(&lock_);
      strlcpy(out_name, name_, min(out_len, Size));
    }
  }

  // Reset the Name to the given data. This will be guaranteed to
  // be nul terminated, so the given data may be truncated.
  zx_status_t set(const char* name, size_t len) __NONNULL((2)) {
    // ignore characters after the first NUL
    len = strnlen(name, len);

    if (len >= Size)
      len = Size - 1;

    AutoSpinLock lock(&lock_);
    memcpy(name_, name, len);
    memset(name_ + len, 0, Size - len);
    return ZX_OK;
  }

  Name& operator=(const Name<Size>& other) {
    if (this != &other) {
      char buffer[Size];
      other.get(Size, buffer);
      set(buffer, Size);
    }
    return *this;
  }

 private:
  // These Names are often included for diagnostic purposes, and
  // access to the Name might be made under various other locks or
  // in interrupt context. So we use a spinlock to serialize.
  mutable SpinLock lock_;
  // This includes the trailing NUL.
  char name_[Size] TA_GUARDED(lock_) = {};
};

}  // namespace fbl

#endif  // ZIRCON_KERNEL_LIB_FBL_INCLUDE_FBL_NAME_H_
